﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace FerrSimplicity
{
    /// <summary>
    /// Represents a 3D transform. It's primarily written for 3D, but will also
    /// work well for 2D. It takes advantage of a dirty flag, so matrix calculations
    /// are only done when necessary
    /// </summary>
    public class Transform3D
    {
        #region Fields
        Vector3 mPosition;
        Vector3 mScale;
        Vector3 mRotation;

        Matrix mTransform;
        Matrix mInvTransform;

        bool mDirty;
        bool mInverseDirty;
        #endregion

        #region Properties
        /// <summary>
        /// The location of the transform in 3D space.
        /// </summary>
        public Vector3 Position
        {
            get { return mPosition; }
            set { mPosition = value; mDirty = true; mInverseDirty = true; }
        }
        /// <summary>
        /// An easy way to set the X and Y components of the position.
        /// </summary>
        public Vector2 Position2D
        {
            get { return new Vector2(mPosition.X, mPosition.Y); }
            set { mPosition.X = value.X; mPosition.Y = value.Y; }
        }
        /// <summary>
        /// A quick way to set the Z value of the position for this transform.
        /// Useful if working as a 2D transform, as this would be the z-depth
        /// of the sprite.
        /// </summary>
        public float   Depth
        {
            get { return mPosition.Z; }
            set { mPosition = new Vector3(mPosition.X, mPosition.Y, value); }
        }
        /// <summary>
        /// The 3D scale of the transform
        /// </summary>
        public Vector3 Scale
        {
            get { return mScale; }
            set { mScale = value; mDirty = true; mInverseDirty = true; }
        }
        /// <summary>
        /// Returns the average of the X Y and Z axes, and sets all the axes to a single value.
        /// </summary>
        public float   ScaleSc
        {
            get { return (mScale.X + mScale.Y + mScale.Z) / 3; }
            set { mScale.X = mScale.Y = mScale.Z = value; }
        }
        /// <summary>
        /// A vector that represents the direction this transform is facing.
        /// </summary>
        public Vector3 Forward
        {
            get { return Vector3.Transform(Vector3.UnitX, Quaternion.CreateFromYawPitchRoll(mRotation.Y, mRotation.X, mRotation.Z)); }
        }
        /// <summary>
        /// A vector that faces perpendicular, to the right of the direction the transform is facing.
        /// </summary>
        public Vector3 Right
        {
            get { return -Transform.Right; }
        }
        /// <summary>
        /// The rotation around the X Y and Z axes.
        /// </summary>
        public Vector3 Rotation
        {
            get { return mRotation; }
            set { mRotation = value; mDirty = true; }
        }
        /// <summary>
        /// An easy way to work with the rotation around the Z axis.
        /// </summary>
        public float   Rotation2D
        {
            get { return mRotation.Z; }
            set { mRotation.Z = value; mDirty = true; }
        }
        /// <summary>
        /// A matrix that represents all the transform information in this class.
        /// </summary>
        public Matrix  Transform
        {
            get { if (mDirty) UpdateTransform(); return mTransform; }
        }
        /// <summary>
        /// A matrix that will undo the transformation from the Transform property. Useful for collision detection.
        /// </summary>
        public Matrix  InverseTransform
        {
            get { if (mInverseDirty) UpdateInverseTransform(); return mInvTransform; }
        }
        #endregion

        #region Constructors
        public Transform3D()
            : this(Vector3.Zero, Vector3.One, Vector3.Zero) { }
        public Transform3D(Vector3 aTranslation)
            : this(aTranslation, Vector3.One, Vector3.Zero) { }
        public Transform3D(Vector3 aTranslation, Vector3 aScale)
            : this(aTranslation, aScale, Vector3.Zero) { }
        public Transform3D(Vector3 aTranslation, Vector3 aScale, Vector3 aRotation)
        {
            mPosition = aTranslation;
            mScale    = aScale;
            mRotation = aRotation;

            mTransform    = Matrix.Identity;
            mInvTransform = Matrix.Identity;

            mDirty        = true;
            mInverseDirty = true;

            UpdateTransform();
            UpdateInverseTransform();
        }

        public Transform3D(Transform3D aToCopy)
            : this(aToCopy.mPosition, aToCopy.mScale, aToCopy.mRotation) { }
        #endregion

        #region Methods
        private void UpdateTransform       ()
        {
            mTransform = Matrix.CreateFromYawPitchRoll(mRotation.Y, mRotation.X, mRotation.Z) * Matrix.CreateScale(mScale) * Matrix.CreateTranslation(mPosition);
            mDirty = false;
        }
        private void UpdateInverseTransform()
        {
            if (mDirty)
                UpdateTransform();
            mInvTransform = Matrix.Invert(mTransform);
            mInverseDirty = false;
        }
        #endregion

        #region Overrides
        public override string ToString()
        {
            return "Translation <" + Math.Round(Position.X, 2) + ", " + Math.Round(Position.Y, 2) + ", " + Math.Round(Position.Z, 2) + ">\n" +
                   "Rotation    <" + Math.Round(Rotation.X, 2) + ", " + Math.Round(Rotation.Y, 2) + ", " + Math.Round(Rotation.Z, 2) + ">\n" +
                   "Scale       <" + Math.Round(Scale   .X, 2) + ", " + Math.Round(Scale   .Y, 2) + ", " + Math.Round(Scale   .Z, 2) + ">";
        }
        #endregion
    }
}
